﻿#region [===== Using =====]
using System;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.Workflow;
#endregion

namespace TPG.SharePoint.Workflow
{
    /// <summary>
    /// A special placeholder which loads the placeholder content based on
    /// metadata in the workflow definition.
    /// </summary>
    /// <remarks>
    /// <para>The placeholder loads the requested data defined in the metadata 
    /// section of the workflow definition. The metadata defines a set of 
    /// name / value pairs which can either indicate a control or static text.</para>
    /// <para>The format used by the placeholder is as follows:
    /// <code>{PageType}_{PlaceHolderType}{Location}{DataType}</code>
    /// For some placeholders the PlaceHolderType is optional.This results in 
    /// the following format:
    /// <code>{PageType}_{Location}{DataType}</code></para>
    /// <para>The PageType is determined by reading the <see cref="IWorkflowPage.PageType" /> 
    /// property.</para>
    /// <para>The PlaceHolderType is determined using the 
    /// <see cref="WorkflowControlPlaceHolder.PlaceHolderType" /> property. This property
    /// is set by the page which houses the placeholder. </para>
    /// <para>The Location is determined using the 
    /// <see cref="WorkflowControlPlaceHolder.Location" /> property. This property
    /// is set by the page which houses the placeholder. </para>
    /// <para>The DataType is determined using the 
    /// <see cref="WorkflowControlPlaceHolder.AllowedContent" /> property. This property
    /// is set by the page which houses the placeholder. </para>
    /// </remarks>
    [PersistChildren(false)]
    [ParseChildren(true)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class WorkflowControlPlaceHolder : PlaceHolder, INamingContainer
    {
        #region [===== Fields =====]
        private Control _formControl;
        #endregion

        #region [===== Properties =====]
        /// <summary>
        /// Gets the hosted control.
        /// </summary>
        public Control Control
        {
            get { return _formControl; }
        }

        /// <summary>
        /// Gets or sets a value indicating if content in the placeholder 
        /// is required.
        /// </summary>
        [Browsable(true)]
        [DefaultValue(false)]
        public bool RequiredContent
        {
            get { return (bool?)ViewState["RequiredContent"] ?? false; }
            set { ViewState["RequiredContent"] = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating the type of content allowed in 
        /// the placeholder.
        /// </summary>
        [Browsable(true)]
        [DefaultValue(WorkflowControlPlaceHolderType.Control | WorkflowControlPlaceHolderType.Text)]
        public WorkflowControlPlaceHolderType AllowedContent
        {
            get { return (WorkflowControlPlaceHolderType?)ViewState["AllowedContent"] ?? 
                WorkflowControlPlaceHolderType.Control | WorkflowControlPlaceHolderType.Text; }
            set { ViewState["AllowedContent"] = value; }
        }

        /// <summary>
        /// Gets or sets the location of the placeholder.
        /// </summary>
        [Browsable(true)]
        public string Location { get; set; }
        /// <summary>
        /// Gets or sets the type of the placeholder.
        /// </summary>
        public string PlaceHolderType { get; set; }

        /// <summary>
        /// Gets or sets a template with default content for the placeholder.
        /// </summary>
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(WorkflowControlPlaceHolder))]
        [Browsable(false)]
        [DefaultValue((string)null)]
        public ITemplate DefaultContent
        {
            get;
            set;
        }
        #endregion

        #region [===== Internal methods =====]
        internal string LookupMetadata(SPWorkflowTemplate template,
            PageType pageType, string dataType, bool includePlaceHolderType)
        {
            StringBuilder lookup = new StringBuilder();
            lookup.AppendFormat("{0}_", pageType.ToString());
            if (includePlaceHolderType)
            {
                lookup.Append(PlaceHolderType);
            }
            lookup.Append(Location);
            lookup.Append(dataType);
            return (string)template[lookup.ToString()];
        }

        internal string RenderAsHtml()
        {
            using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
            {
                using (HtmlTextWriter htmlWriter = new HtmlTextWriter(writer))
                {
                    bool initialVisible = this.Visible;
                    this.Visible = true;
                    this.RenderControl(htmlWriter);
                    this.Visible = initialVisible;
                }

                return writer.ToString();
            }
        }
        #endregion

        #region [===== Protected methods =====]
        /// <summary>
        /// Initializes the placeholder by loading the hosted control from the 
        /// workflow metadata.
        /// </summary>
        /// <param name="e">An EventArgs containing event data.</param>
        protected override void OnInit(EventArgs e)
        {
            // verify the control is in a workflow page            
            IWorkflowPage workflowPage = this.Page as IWorkflowPage;
            if (workflowPage == null)
            {
                throw new WorkflowException("This control must be placed on a page implementing IWorkflowPage.");
            }

            OnCreatingControl(EventArgs.Empty);

            if ((this.AllowedContent & WorkflowControlPlaceHolderType.Control) == 
                WorkflowControlPlaceHolderType.Control)
            {
                string formUrl = LookupMetadata(
                    workflowPage.WorkflowTemplate,
                    workflowPage.PageType,
                    "Url", true);
                if (String.IsNullOrEmpty(formUrl))
                {
                    formUrl = LookupMetadata(
                        workflowPage.WorkflowTemplate,
                        workflowPage.PageType,
                        "Url", false);
                }
                if (!string.IsNullOrEmpty(formUrl))
                    _formControl = CreateFormControl(formUrl);
            }
            if (_formControl == null)
            {
                if ((this.AllowedContent & WorkflowControlPlaceHolderType.Text) == WorkflowControlPlaceHolderType.Text)
                {
                    string text = LookupMetadata(workflowPage.WorkflowTemplate, 
                        workflowPage.PageType, "Text", true);
                    if (String.IsNullOrEmpty(text))
                    {
                        text = LookupMetadata(
                            workflowPage.WorkflowTemplate,
                            workflowPage.PageType,
                            "Text", false);
                    }
                    if (!string.IsNullOrEmpty(text))
                        _formControl = CreateTextControl(text);
                }
            }

            if ((_formControl == null) && this.RequiredContent)
                throw new WorkflowException(string.Format(CultureInfo.CurrentCulture, "The control with ID '{0}' requires content.", this.ID));

            if (_formControl != null)
            {
                this.Controls.Add(_formControl);
            }
            else
            {
                if (DefaultContent != null)
                    DefaultContent.InstantiateIn(this);
            }
            
            OnCreatedControl(EventArgs.Empty);       
            base.OnInit(e);
        }

        /// <summary>
        /// Raises the <see cref="WorkflowControlPlaceHolder.CreatingControl" /> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs" /> containing
        /// event data.</param>
        protected virtual void OnCreatingControl(EventArgs e)
        {
            if (CreatingControl != null)
            {
                CreatingControl(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="WorkflowControlPlaceHolder.CreatedControl" /> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs" /> containing
        /// event data.</param>
        protected virtual void OnCreatedControl(EventArgs e)
        {
            if (CreatedControl != null)
            {
                CreatedControl(this, e);
            }
        }
        /// <summary>
        /// Creates a control based on the control virtual path.
        /// </summary>
        /// <param name="formUrl">The virtual path to the control to load.</param>
        /// <returns>The loaded control.</returns>
        protected virtual Control CreateFormControl(string formUrl)
        {
            return WorkflowControl.CreateFormControl(this.Page, formUrl);
        }
        /// <summary>
        /// Creates a control displaying a static piece of text.
        /// </summary>
        /// <param name="text">The text to display in the control.</param>
        /// <returns>The loaded control.</returns>
        protected virtual Control CreateTextControl(string text)
        {
            Literal control = new Literal();
            control.Text = text;
            return control;
        }
        #endregion

        #region [===== Events =====]
        /// <summary>
        /// An event firing before the hosted control is created.
        /// </summary>
        public event EventHandler CreatingControl;
        /// <summary>
        /// An event firing after the hosted control was created.
        /// </summary>
        public event EventHandler CreatedControl;
        #endregion
    }
}
